home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / SourceCode / daymisckit_proj / Palettes / DAYString / DAYString.m < prev    next >
Text File  |  1995-06-12  |  29KB  |  1,080 lines

  1. //
  2. //    DAYString.m -- a generic class to simplify manipulation of (char *)'s
  3. //        Written by Don Yacktman (c) 1993 by Don Yacktman.
  4. //                Version 1.2  All rights reserved.
  5. //
  6. //        This is a free object!  Contact the author for the latest version.
  7. //        Don Yacktman, 4279 N. Ivy Lane, Provo, UT, 84604
  8. //        e-mail:  Don_Yacktman@byu.edu
  9. //
  10. //        You may use and copy this class freely as long as you
  11. //        comply with the following terms:
  12. //            (1) Do not remove the author's name or any of the
  13. //                copyright notices from this file.
  14. //            (2) If you redistribute an application which uses this
  15. //                object, you must either include the source code for
  16. //                this object with the application or state in your
  17. //                application's documentation that you (a) use this
  18. //                object and (b) where to obtain the source code for
  19. //                the object.
  20. //            (3) In no way shall the author or his employer(s) be held
  21. //                responsible for any damages caused by what this object
  22. //                does or does not do.
  23. //            (4) You have no warranty whatsoever that this object is
  24. //                good for any purpose at all.  If you find it useful
  25. //                for something, consider yourself lucky and leave it at that.
  26. //
  27.  
  28. //
  29. //    And now for an implementation note... (philosophy of this class)
  30. //    The object is to make a bulletproof class.  That means that NULL
  31. //    pointers and values that are out of range are ignored, or that
  32. //    nil is returned, or that the object tries to do what would be the
  33. //    intelligent thing to do in such boundary cases.  Obviously, all
  34. //    this error checking costs you something:  speed.  On the other hand,
  35. //    your code shouldn't end up with anywhere near as many bus errors
  36. //    and other silliness!  (This is not a panacea; don't rely on the
  37. //    DAYString to get everything right for you; if a boudary condition
  38. //    might exist, do be sure to check return values as they can signal
  39. //    problems for you.)  There is no substitue for good programming,
  40. //    but hopefully this class will help you survive silly mistakes.
  41. //    Most of the methods send to other methods to do the real work;
  42. //    it would run faster to implement each method separately, since
  43. //    some methods would then not need to do certain checks, and so on.
  44. //    On the other hand, it is a whole lot easier to maintain this code
  45. //    since there are a handful of core methods that do all the work.
  46. //    Someday I may attempt to make a class DAYFastString that will
  47. //    be optimized for speed... but only if there is either (1) a *whole*
  48. //    lot of demand for it, or (2) someone pays me to write it.  :-)
  49. //
  50. //    Returns:  I try to follow NeXT conventions.  Return self, nil, or
  51. //        whatever makes sense in the context.  Hopefully your idea of
  52. //        what makes sense will coincide with mine.  Read the docs...
  53. //
  54. //    For this release (1.2), I have gone over every single method and
  55. //        tried to make sure it behaved sanely on all possible boundary
  56. //        cases, especially NULL pointers.  If there is any way to
  57. //        break this class, or cause a bus error in here, etc. or any
  58. //        other bugs I WANT to know about them... be sure to send bug
  59. //        reports to me at Don_Yacktman@byu.edu so I can fix them.  I
  60. //        want this class to be bulletproof.  Note that there are still
  61. //        a few possibilities to get memory leaks here.  I want to fix
  62. //        those, if at all possible, so remind me of the ones you find.
  63. //        I got rid of the most obvious and common ones.
  64. //
  65. //    One memory leak possibility, and this happens in the test app a
  66. //    little bit:  you call a method that returns a new object, but
  67. //    then do something like this:
  68. //        printf("%s", [[aString right:5] stringValue]);
  69. //    A new instance is created, but never freed.  I need a way around this,
  70. //    like a method -stringValueAndFree that returns a const char * and
  71. //    then free itself.  The trick is that the returned char * is still
  72. //    around.  The most elegant solution I can think of is to run the
  73. //    pointer through NXUniqueString() first, and then return it, after
  74. //    freeing the object.  This way the pointer is never lost, and you
  75. //    won't really leak memory, but the string space sure will get cluttered.
  76. //    If you have an idea on how to fix this problem, let me know.  I like
  77. //    the convenience of the above printf, but do not like the memory leak.
  78. //    You could always to this:
  79. //        temp = [aString right:5];
  80. //        printf("%s", [temp stringValue]);
  81. //        [temp free];
  82. //    But that's a pain in the *ss...
  83. //    Well, send me your suggestions.
  84. //
  85.  
  86. #import <daymisckit/DAYString.h>
  87. #import <strings.h>
  88.  
  89. @implementation DAYString
  90.  
  91. - init
  92. {
  93.      [super init];
  94.      [self setStringOrderTable:NXDefaultStringOrderTable()];
  95.      if (buffer) free(buffer);
  96.      buffer = NULL;
  97.      length = 0;
  98.      _length = 0;
  99.      return self;
  100. }
  101.  
  102. - initString:(const char *)aString
  103. {
  104.     [self init];
  105.     return [self setStringValue:aString];
  106. }
  107.  
  108. - setStringOrderTable:(NXStringOrderTable *)table
  109. {
  110.     if (table) orderTable = table;
  111.     else orderTable = NXDefaultStringOrderTable(); // just in case...
  112.     return self;
  113. }
  114.  
  115. - (NXStringOrderTable *)stringOrderTable
  116. {
  117.     return orderTable;
  118. }
  119.  
  120. - allocateBuffer:(int)size
  121. {
  122.     return [self allocateBuffer:size fromZone:[self zone]];
  123. }
  124.  
  125. - allocateBuffer:(int)size fromZone:(NXZone *)zone
  126. {
  127.     if (size <= _length) return self;
  128.     [self freeString];
  129.     if (!size) return self;
  130.     buffer = (char *)NXZoneMalloc(zone, size);
  131.     buffer[0] = 0;
  132.     _length = size;
  133.  
  134.     return self;
  135. }
  136.  
  137. - setStringValue:(const char *)aString
  138. {
  139.     return [self setStringValue:aString fromZone:[self zone]];
  140. }
  141.  
  142. - setStringValue:(const char *)aString fromZone:(NXZone *)zone
  143. {
  144.     if (!aString) return self; // use -freeString to set to "NULL"
  145.     // Note that I could have used NXCopyStringBufferFromZone() here
  146.     // instead.  This works just as well, but it may be slower...
  147.     // I haven't checked that out, though.  It might be worth doing.
  148.     [self allocateBuffer:strlen(aString)+1 fromZone:zone];
  149.     strcpy(buffer, aString);
  150.     length = strlen(buffer);
  151.     return self;
  152. }
  153.  
  154. - takeStringValue:sender
  155. { // if no string value, return nil; the user is expecting a changed string
  156.     if (![sender respondsTo:@selector(stringValue)]) return nil;
  157.     return [self setStringValue:[sender stringValue] fromZone:[self zone]];
  158. }
  159.  
  160. - takeStringValue:sender fromZone:(NXZone *)zone
  161. {
  162.     if (![sender respondsTo:@selector(stringValue)]) return nil;
  163.     return [self setStringValue:[sender stringValue] fromZone:zone];
  164. }
  165.  
  166. - (const char *)stringValue
  167. {
  168.     return buffer;
  169. }
  170.  
  171. - read:(NXTypedStream *)stream
  172. {
  173.     int tempLength; char *tBuf;
  174.     [super read:stream];
  175.     NXReadType(stream, "i", &tempLength);
  176.     [self allocateBuffer:tempLength fromZone:[self zone]];
  177.     NXReadType(stream, "*", &tBuf);
  178.     if (tBuf) strcpy(buffer, tBuf);
  179.     if (!tBuf && buffer) buffer[0] = '\0'; // NULL string?
  180.     return self;
  181. }
  182.  
  183. - write:(NXTypedStream *)stream
  184. {
  185.     [super write:stream];
  186.     NXWriteType(stream, "i", &_length);
  187.     NXWriteType(stream, "*", &buffer);
  188.     return self;
  189. }
  190.  
  191. - copyFromZone:(NXZone *)zone
  192. {
  193.     DAYString *myCopy = [super copyFromZone:zone];
  194.     // force child to have it's own copy of the string buffer
  195.     [myCopy _unhookBuffer];
  196.     [myCopy allocateBuffer:_length fromZone:zone];
  197.     [myCopy setStringValue:buffer fromZone:zone];
  198.     return myCopy;
  199. }
  200.  
  201. - _unhookBuffer
  202. { // used by the copy method so that we don't free the buffer from orig. 
  203.     buffer = NULL; _length = 0;
  204.     return self;
  205. }
  206.  
  207. - freeString
  208. {
  209.     if(buffer) free(buffer);
  210.     buffer = NULL;
  211.     length = 0;
  212.     _length = 0;
  213.     return self;
  214. }
  215.  
  216. - free
  217. {
  218.      [self freeString];
  219.      return [super free];
  220. }
  221.  
  222. - cat:(const char *)aString
  223. {
  224.     return [self cat:aString n:strlen(aString) fromZone:[self zone]];
  225. }
  226.  
  227. - cat:(const char *)aString n:(int)n
  228. {
  229.     return [self cat:aString n:n fromZone:[self zone]];
  230. }
  231.  
  232. - cat:(const char *)aString fromZone:(NXZone *)zone
  233. {
  234.     return [self cat:aString n:strlen(aString) fromZone:zone];
  235. }
  236.  
  237. - cat:(const char *)aString n:(int)n fromZone:(NXZone *)zone
  238. {
  239.     char *newBuffer; int newSize;
  240.     if (!(aString || buffer)) return nil;
  241.     if (!buffer) return [self setStringValue:aString fromZone:zone];
  242.     if (!aString) return self;
  243.     if (n > strlen(aString)) n = strlen(aString);
  244.     newSize = length + n + 1;
  245.     if (newSize > _length) {
  246.         newBuffer = (char *)NXZoneMalloc(zone, newSize);
  247.         _length = newSize;
  248.         newBuffer[0] = '\0';
  249.         strcat(newBuffer, buffer);
  250.         strncat(newBuffer, aString, n);
  251.         free(buffer);
  252.         buffer = newBuffer;
  253.     } else  strncat(buffer, aString, n);
  254.     length = strlen(buffer);
  255.     return self;
  256. }
  257.  
  258. - concatenate:sender
  259. { // note return self here; assume that there's nothing to add...
  260.     if (![sender respondsTo:@selector(stringValue)]) return self;
  261.     return [self cat:[sender stringValue]
  262.                  n:strlen([sender stringValue])
  263.                  fromZone:[self zone]];
  264. }
  265.  
  266. - concatenate:sender n:(int)n
  267. {
  268.     if (![sender respondsTo:@selector(stringValue)]) return self;
  269.     return [self cat:[sender stringValue] n:n fromZone:[self zone]];
  270. }
  271.  
  272. - concatenate:sender fromZone:(NXZone *)zone
  273. {
  274.     if (![sender respondsTo:@selector(stringValue)]) return self;
  275.     return [self cat:[sender stringValue]
  276.             n:strlen([sender stringValue]) fromZone:zone];
  277. }
  278.  
  279. - concatenate:sender n:(int)n fromZone:(NXZone *)zone
  280. {
  281.     if (![sender respondsTo:@selector(stringValue)]) return self;
  282.     return [self cat:[sender stringValue] n:n fromZone:zone];
  283. }
  284.  
  285. - (const char *)strstr:(const char *)subString
  286. {
  287.     if (!(buffer||subString)) return NULL;
  288.     return strstr(buffer, subString);
  289. }
  290.  
  291. - subStringRight:subString
  292. {
  293.     const char *sub, *sub2;
  294.     if (!buffer) return nil;
  295.     if ([subString respondsTo:@selector(stringValue)])
  296.         sub = [subString stringValue];
  297.     else return nil;    // error if can't get string value
  298.     if (!sub) return nil;
  299.     sub2 = strstr(buffer,sub);
  300.     if (!sub2) return nil;
  301.     return [[DAYString allocFromZone:[self zone]]
  302.             initString:sub2];
  303. }
  304.  
  305. - subStringLeft:subString
  306. {
  307. /*    Old implementation:  ugly and has potential memory leak
  308.     const char *sub;
  309.     char *tempString, *temp2;
  310.     id retVal = [DAYString alloc];
  311.     
  312.     if ([subString respondsTo:@selector(stringValue)])
  313.         sub = [subString stringValue];
  314.     else return nil;    // error if can't get string value
  315.     tempString = NXCopyStringBufferFromZone(buffer, [self zone]);
  316.     temp2 = strstr(tempString, sub); 
  317.     if (temp2) {
  318.         temp2[0] = '\0';    // terminate it early
  319.         [retVal initString:tempString];
  320.     } else { // substring not found
  321.         return [self copy];
  322.     }
  323.     free(tempString);
  324.     return retVal;
  325. */
  326.     const char *sub;
  327.     char *sub2;
  328.     int spot;
  329.  
  330.     if ([subString respondsTo:@selector(stringValue)])
  331.         sub = [subString stringValue];
  332.     else return nil;    // error if can't get string value
  333.     if (!sub) return nil;
  334.     if (!(sub2 = strstr(buffer, sub))) return nil;
  335.     spot = (int)sub2 - (int)buffer - 1;
  336.     if (spot < 0) return nil;
  337.     return [self midFrom:0 to:spot];
  338. }
  339.  
  340. - (int)length
  341. {
  342.     return length;
  343. }
  344.  
  345. - (BOOL)isEqual:anObject
  346. {
  347.     if (anObject == self) return YES;
  348.     // doesn't have to be a DAYString object to be equal...
  349.     if ([anObject respondsTo:@selector(stringValue)]) {
  350.         if (!NXOrderStrings(buffer, [anObject stringValue],
  351.                 YES, -1, orderTable)) return YES;
  352.     }
  353.     return NO;
  354. }
  355.  
  356. - (int)compareTo:sender
  357. {
  358.     return [self compareTo:sender n:(-1) caseSensitive:YES];
  359. }
  360.  
  361. - (int)compareTo:sender n:(int)n
  362. {
  363.     return [self compareTo:sender n:n caseSensitive:YES];
  364. }
  365.  
  366. - (int)cmp:(const char *)aString
  367. {
  368.     if (!aString && !buffer) return 0;        // both NULL, so "equal"
  369.     if (!aString || !buffer) return -1;    // only one is NULL, so not equal
  370.     return strcmp(buffer, aString);
  371. }
  372.  
  373. - (int)cmp:(const char *)aString n:(int)n
  374. {
  375.     if (!aString && !buffer) return 0;        // both NULL, so "equal"
  376.     if (!aString || !buffer) return -1;    // only one is NULL, so not equal
  377.     // we don't check n; use strncmp's behavior here.
  378.     return strncmp(buffer, aString, n);
  379. }
  380.  
  381. - (int)compareTo:sender caseSensitive:(BOOL)sense
  382. {
  383.     return [self compareTo:sender n:(-1) caseSensitive:sense];
  384. }
  385.  
  386. - (int)compareTo:sender n:(int)n caseSensitive:(BOOL)sense
  387. {
  388.     if (![sender respondsTo:@selector(stringValue)]) return 1; // !=
  389.     if (!buffer && ![sender stringValue]) return 0;        // both are NULL
  390.     if (!(buffer && [sender stringValue])) return 1;    // one is NULL
  391.     return NXOrderStrings(buffer, [sender stringValue], sense, n, orderTable);
  392. }
  393.  
  394. - (int)casecmp:(const char *)aString
  395. {
  396.     if (!aString && !buffer) return 0;        // both NULL, so "equal"
  397.     if (!aString || !buffer) return -1;    // only one is NULL, so not equal
  398.     return strcasecmp(buffer, aString);
  399. }
  400.  
  401. - (int)casecmp:(const char *)aString n:(int)n
  402. {
  403.     if (!aString && !buffer) return 0;        // both NULL, so "equal"
  404.     if (!aString || !buffer) return -1;    // only one is NULL, so not equal
  405.     // we don't check n; use strncasecmp's behavior here.
  406.     return strncasecmp(buffer, aString, n);
  407. }
  408.  
  409. - left:(int)count
  410. {
  411.     return [self left:count fromZone:[self zone]];
  412. }
  413.  
  414. - right:(int)count
  415. {
  416.     return [self right:count fromZone:[self zone]];
  417. }
  418.  
  419. - midFrom:(int)start to:(int)end
  420. {
  421.     return [self midFrom:start to:end fromZone:[self zone]];
  422. }
  423.  
  424. - midFrom:(int)start length:(int)len
  425. {
  426.     return [self midFrom:start length:len fromZone:[self zone]];
  427. }
  428.  
  429. - left:(int)count fromZone:(NXZone *)zone
  430. {
  431.     char smash = buffer[count];
  432.     id newString;
  433.     if (!buffer) return nil;
  434.     if (count >= length) return [self copyFromZone:zone];
  435.     newString = [[DAYString allocFromZone:zone] init];
  436.     buffer[count] = '\0';
  437.     [newString setStringValue:buffer fromZone:zone];
  438.     buffer[count] = smash;
  439.     return newString;
  440. }
  441.  
  442. - right:(int)count fromZone:(NXZone *)zone
  443. {
  444.     id newString;
  445.     if (!buffer) return nil;
  446.     if (count >= length) return [self copyFromZone:zone];
  447.     newString = [[DAYString allocFromZone:zone] init];
  448.     [newString setStringValue:&buffer[length - count] fromZone:zone];
  449.     return newString;
  450. }
  451.  
  452. - midFrom:(int)start to:(int)end fromZone:(NXZone *)zone
  453. {
  454.     char smash = buffer[end+1];
  455.     id newString;
  456.     if (!buffer) return nil;
  457.     if ((end < 0) || (start >= length)) return nil;
  458.     if (end >= length) end = length-1;
  459.     if (start < 0) start = 0;
  460.     newString = [[DAYString allocFromZone:zone] init];
  461.     buffer[end+1] = '\0'; // inclusive; end-1 is not. (well, end isn't either, anymore -- Carl)
  462.     [newString setStringValue:&buffer[start] fromZone:zone];
  463.     buffer[end+1] = smash;
  464.     return newString;
  465. }
  466.  
  467. - midFrom:(int)start length:(int)len fromZone:(NXZone *)zone
  468. {
  469.     return [self midFrom:start to:(start + len - 1) fromZone:zone];
  470. /*    faster to have our own code here, but rather than maintain this,
  471.     we use the cover above.  I'm keeping the code around in case I
  472.     decide to do DAYFastString.
  473.     
  474.     register int spot = start + len;
  475.     char smash = buffer[spot];
  476.     id newString;
  477.     if (!buffer) return nil;
  478.     if ((end < 0) || (start >= length)) return nil;
  479.     if (start < 0) start = 0;
  480.     if (start+len-1 >= length) len = length-start;
  481.     newString = [[DAYString allocFromZone:zone] init];
  482.     buffer[spot] = '\0';
  483.     [newString setStringValue:&buffer[start] fromZone:zone];
  484.     buffer[spot] = smash;
  485.     return newString;
  486. */
  487. }
  488.  
  489. - encrypt:salt
  490. {    // encrypt as a UNIX password using the DAYString "salt" as the salt...
  491.     // see crypt(3) for more info
  492.     // The cast prevents a warning:  -stringValue returns a const char *.
  493.     // Assuming crypt() doesn't change the salt, the cast is OK.
  494.     char *strv;
  495.     if ([salt respondsTo:@selector(stringValue)])
  496.         strv = (char *)[salt stringValue];
  497.     else return nil;
  498.     if (!(buffer && strv)) return nil;
  499.     return [[DAYString alloc] initString:crypt(buffer, strv)];
  500. }
  501.  
  502. // NXTransport protocol implementation:
  503. - encodeUsing:(id <NXEncoding>)portal
  504. {
  505.     [portal encodeData:&_length ofType:"i"];
  506.     [portal encodeData:&length ofType:"i"];
  507.     [portal encodeData:&buffer ofType:"*"];
  508.     return self;
  509. }
  510.  
  511. - decodeUsing:(id <NXDecoding>)portal
  512. {
  513.     int newLen;
  514.     [self freeString];
  515.     [portal decodeData:&newLen ofType:"i"];
  516.     [self allocateBuffer:newLen];
  517.     [portal decodeData:&length ofType:"i"];
  518.     [portal decodeData:&buffer ofType:"*"];
  519.     return self;
  520. }
  521.  
  522. - encodeRemotelyFor:(NXConnection *)connection
  523.     freeAfterEncoding:(BOOL *)flagp isBycopy:(BOOL)isByCopy
  524. {
  525.     if (isByCopy) {
  526.         *flagp = NO; // object will copy.
  527.         return self; //encode object (copy it)
  528.     }
  529.     *flagp = NO; // object will copy.
  530.     // super will encode the proxy otherwise
  531.     return [super encodeRemotelyFor:connection
  532.                 freeAfterEncoding:flagp isBycopy:isByCopy];
  533. }
  534.  
  535. // Interface Builder support
  536. - (const char *)getInspectorClassName { return "DAYStringInspector"; }
  537. - (NXImage *)getIBImage { return [NXImage findImageNamed:"DAYStringObj"]; }
  538.  
  539. // some other new methods V1.2 -- don
  540.  
  541. - extractPart:(int)n useAsDelimiter:(char)c caseSensitive:(BOOL)sense fromZone:(NXZone *)zone
  542. {
  543.     int left = ((n == DAY_FIRST) ? 0 :
  544.             ( n == DAY_LAST ?
  545.                 ([self rspotOf:c occurenceNum:1 caseSensitive:sense] + 1) :
  546.                 ([self spotOf:c occurenceNum:(n-1) caseSensitive:sense] + 1)));
  547.     int right = ((n == DAY_FIRST) ?
  548.             ([self spotOf:c occurenceNum:1 caseSensitive:sense] - 1) :
  549.             ( n == DAY_LAST ? length :
  550.                 [self spotOf:c occurenceNum:n caseSensitive:sense] - 1));
  551.     return [self midFrom:left to:right fromZone:zone];
  552. }
  553.  
  554. - extractPart:(int)n useAsDelimiter:(char)c caseSensitive:(BOOL)sense
  555. {
  556.     return [self extractPart:n useAsDelimiter:c
  557.             caseSensitive:sense fromZone:[self zone]];
  558. }
  559.  
  560. - extractPart:(int)n useAsDelimiter:(char)c fromZone:(NXZone *)zone
  561. {
  562.     return [self extractPart:n useAsDelimiter:c
  563.             caseSensitive:YES fromZone:zone];
  564. }
  565.  
  566. - extractPart:(int)n useAsDelimiter:(char)c
  567. {
  568.     return [self extractPart:n useAsDelimiter:c\
  569.             caseSensitive:YES fromZone:[self zone]];
  570. }
  571.  
  572. - fileNameFromZone:(NXZone *)zone
  573. {
  574.     return [self extractPart:DAY_LAST useAsDelimiter:'/'
  575.                  caseSensitive:YES fromZone:[self zone]];
  576. }
  577.  
  578. - fileName
  579. {
  580.     return [self fileNameFromZone:[self zone]];
  581. }
  582.  
  583. - pathNameFromZone:(NXZone *)zone
  584. {
  585.     return [self left:[self rspotOf:'/'] fromZone:zone];
  586. }
  587.  
  588. - pathName
  589. {
  590.     return [self pathNameFromZone:[self zone]];
  591. }
  592.  
  593. //Carl's additions....
  594.  
  595. + newWithString:(const char *)aString
  596. // I just got tired of typing [[[DAYString alloc] init] setStringValue:xxx] 
  597. {
  598.     id newString = [[DAYString alloc] init]; // should I use [[self alloc]... ?
  599.     if ([newString setStringValue:aString]) return newString;
  600.     [newString free];
  601.     return nil;
  602. }
  603.  
  604. - wordNum :(int) num
  605. // returns a new String containing the numth word in buffer
  606. // if numth word does not exist, returns nil.
  607. {
  608.     int i = 0;
  609.     int currword = 1;
  610.     int spot = 0; 
  611.     int spot2 = 0;
  612.  
  613.     if (!buffer) return nil;
  614.     while ((currword <= num) && (i <= length)) {
  615.         while ((NXIsSpace(buffer[i])) && (i <= length)) i++;
  616.         spot2 = i;
  617.         while ((!NXIsSpace(buffer[i])) && (i <= length)) i++;
  618.         spot = i;
  619.         currword++;
  620.     }
  621.     if (spot == spot2) return nil;
  622.     return [self midFrom :spot2 length:spot-spot2];
  623. }
  624.  
  625. - (int)numWords
  626. { //counts the number of words in buffer.
  627.     int i=0;
  628.     int currword = 0;
  629.  
  630.     if (!buffer) return 0;  
  631.     while (i <= length) {
  632.         while ((NXIsSpace(buffer[i])) && (i <= length)) i++;
  633.         while ((!NXIsSpace(buffer[i])) && (i <= length)) i++;
  634.         currword++;
  635.     }
  636.     if (NXIsSpace(buffer[length-1])) currword--;
  637.     return currword;
  638. }
  639.  
  640. - trimLeadSpaces
  641. { // removes any leading spaces from buffer
  642.   int i = 0;
  643.   id tmpStr;
  644.   
  645.   if (!buffer) return self;
  646.   while (buffer[i] == ' ') i++;
  647.   if (i==0) return self;
  648.   tmpStr = [self right:length-i];
  649.   [self takeStringValue:tmpStr];
  650.   [tmpStr free];
  651.   return self;
  652. }
  653.  
  654. - trimTailSpaces
  655. // removes any trailing spaces from buffer
  656. {
  657.   int i = length;
  658.   id tmpStr;
  659.   
  660.   if (!buffer) return self;
  661.   while (buffer[i-1] == ' ') i--;
  662.   if (i==length) return self;
  663.   tmpStr = [self left:i];
  664.   [self takeStringValue:tmpStr];
  665.   [tmpStr free];
  666.   return self;
  667. }
  668.  
  669.  
  670. - trimSpaces
  671. // takes off leading and trailing spaces of the buffer
  672. {
  673.   return [[self trimLeadSpaces] trimTailSpaces];
  674. }
  675.  
  676. - reverse
  677. // reverses the characters in the buffer.  If it's a palindrome, you won't
  678. // notice much of a difference :-)
  679. {
  680.   char tmp[length+1];
  681.   int j=0;
  682.   int i;
  683.   
  684.   if (length <= 1) return self;
  685.   for (i=length-1;i>=0;i--)
  686.    {
  687.      tmp[j] = buffer[i];
  688.      j++;
  689.    }
  690.   tmp[length] = 0;
  691.   if (length != 0) [self setStringValue:tmp];
  692.   return self;
  693. }
  694.   
  695.   
  696. - toUpper
  697. // converts any lowercase characters in buffer to uppercase
  698. {
  699.   int i;
  700.   
  701.   for (i=0;i<length;i++)
  702.    {
  703.      if (NXIsLower(buffer[i])) buffer[i] = NXToUpper(buffer[i]);
  704.    }
  705.   
  706.   return self;
  707. }
  708.  
  709. - toLower
  710. // converts any uppercase chars in buffer to lowercase
  711. {
  712.   int i;
  713.   
  714.   for (i=0;i<length;i++)
  715.    {
  716.      if (NXIsUpper(buffer[i])) buffer[i] = NXToLower(buffer[i]);
  717.    }
  718.   
  719.   return self;
  720. }
  721.  
  722. - insert:(const char *)aString at:(int)index
  723. // inserts given string into buffer starting at index.
  724. // (the first character is position #0)
  725. {
  726.   id temp1;
  727.   id temp2;
  728.   
  729.   if ((aString == NULL) || (strlen(aString)<=0)) return self;
  730.   if (index < 0) index = 0;         
  731.   if (index >= length) return [self cat:aString];
  732.   
  733.   temp1 = [self left:index];
  734.   temp2 = [self right:length-index];  
  735.   [[temp1 cat:aString] concatenate:temp2];
  736.   [self takeStringValue:temp1];
  737.  
  738.   [temp1 free];
  739.   [temp2 free];
  740.   return self;
  741. }
  742.  
  743. - insertString:(id)sender at:(int)index
  744. // cover for insert:at: for a String object
  745. {
  746.   if (![sender respondsTo:@selector(stringValue)]) return self;
  747.   return [self insert:[sender stringValue] at:index];
  748. }
  749.  
  750. - insertChar:(char)aChar at:(int)index
  751. {
  752.   id tempStr;
  753.   id retval;
  754.   
  755.   if (aChar == 0) return self;    // or should this return nil? DAY: leave it
  756.   
  757.   tempStr = [[[[DAYString alloc] init] allocateBuffer:2] addChar:aChar];
  758.   retval = [self insert:[tempStr stringValue] at:index];
  759.   [tempStr free];
  760.   return retval;
  761. }
  762.   
  763. - (char) charAt:(int)index
  764. {
  765.   if ((index < 0) || (index >length-1)) return 0;
  766.   return (char)buffer[index];
  767. }
  768.  
  769. - removeFrom:(int)index length:(int)len
  770. { // to avoid memory leaks, this should NEVER return nil!
  771.   id temp1,temp2;
  772.   
  773.   if (!buffer) return self;    // everything's already gone
  774.   if (len <= 0) return self; // noting to remove
  775.   // DAY: should I presume to fix index<0 like so? or just index = 0?
  776.   // or just return self?
  777.   if (index < 0) { length += index; index = 0; if (len <= 0) return self; }
  778.   if (index > length - 1) return self; // nothing out there
  779.   temp1 = [self left:index];
  780.   temp2 = [self midFrom:index+len to:length];
  781.   [temp1 concatenate:temp2];
  782.   [self takeStringValue:temp1];
  783.   [temp1 free];
  784.   [temp2 free];
  785.   return self;
  786. }
  787.  
  788. - removeFrom:(int)start to:(int)end
  789. {
  790.   return [self removeFrom:start length:end-start+1];
  791. }
  792.  
  793. - replaceFrom:(int)start length:(int)len with:(const char *)aString
  794. {
  795.   return [[self removeFrom:start length:len] insert:aString at:start];
  796. }
  797.  
  798. - replaceFrom:(int)start to:(int)end with:(const char *)aString
  799. {
  800.   return [[self removeFrom:start to:end] insert:aString at:start];
  801. }
  802.  
  803. - replaceFrom:(int)start length:(int)len withString:(id)sender
  804. {
  805.   if (![sender respondsTo:@selector(stringValue)]) return self;
  806.   return [self replaceFrom:start length:len with:[sender stringValue]];
  807. }
  808.  
  809. - replaceFrom:(int)start to:(int)end withString:(id)sender
  810. {
  811.   if (![sender respondsTo:@selector(stringValue)]) return self;
  812.   return [self replaceFrom:start to:end with:[sender stringValue]];
  813. }
  814.  
  815. - replace:(const char *)subString with:(const char *)newString
  816. {
  817.   const char *spot;
  818.   
  819.   if (spot = [self strstr:subString])
  820.     [self replaceFrom:(int)(spot-buffer) length:strlen(subString) with:newString];
  821.   return self;
  822. }
  823.  
  824. - replace:(const char *)subString withString:(id)sender
  825. {
  826.   if (![sender respondsTo:@selector(stringValue)]) return self;
  827.   return [self replace:subString with:[sender stringValue]];
  828. }
  829.  
  830. - (int)spotOf:(char)aChar
  831. {
  832.   return [self spotOf:aChar occurenceNum:1 caseSensitive:YES];
  833. }
  834.  
  835. - (int)spotOf:(char)aChar caseSensitive:(BOOL)sense
  836. {
  837.   return [self spotOf:aChar occurenceNum:1 caseSensitive:sense];
  838. }
  839.  
  840. - (int)spotOf:(char)aChar occurenceNum:(int)n
  841. {
  842.   return [self spotOf:aChar occurenceNum:n caseSensitive:YES];
  843. }
  844.  
  845. - (int)rspotOf:(char)aChar
  846. {
  847.   return [self rspotOf:aChar occurenceNum:1 caseSensitive:YES];
  848. }
  849.  
  850. - (int)rspotOf:(char)aChar caseSensitive:(BOOL)sense
  851. {
  852.   return [self rspotOf:aChar occurenceNum:1 caseSensitive:sense];
  853. }
  854.  
  855. - (int)rspotOf:(char)aChar occurenceNum:(int)n
  856. {
  857.   return [self rspotOf:aChar occurenceNum:n caseSensitive:YES];
  858. }
  859.  
  860. - (int)spotOf:(char)aChar occurenceNum:(int)n caseSensitive:(BOOL)sense
  861. {
  862.   int currnum = 0;
  863.   int count = 0;
  864.   
  865.   if (n<1) return -1;
  866.   
  867.   while ((currnum < n) && (count<length)) {
  868.     if (!sense) {
  869.       if (NXToUpper(buffer[count]) == NXToUpper(aChar)) currnum++;
  870.      }
  871.     else {
  872.       if (buffer[count] == aChar) currnum++;
  873.      }
  874.     count++;
  875.    }
  876.   if (currnum != n) return -1;
  877.   return (count-1);
  878. }
  879.  
  880. - (int)rspotOf:(char)aChar occurenceNum:(int)n caseSensitive:(BOOL)sense
  881. {
  882.   int currnum = 0;
  883.   int count = length-1;
  884.   
  885.   if (n<1) return -1;
  886.   
  887.   while ((currnum < n) && (count >= 0)) {
  888.     if (!sense) {
  889.       if (NXToUpper(buffer[count]) == NXToUpper(aChar)) currnum++;
  890.      }
  891.     else {
  892.       if (buffer[count] == aChar) currnum++;
  893.      }
  894.     count--;
  895.    }
  896.   if (currnum != n) return -1;
  897.   return (count+1);
  898. }
  899.  
  900. - addChar:(char)aChar
  901. {
  902.   if (aChar) [self cat:&aChar n:1];
  903.   return self;
  904. }
  905.  
  906. - squashSpaces
  907. {
  908.   int count = 0;
  909.   id tempStr;
  910.   
  911.   if (!buffer) return self;
  912.   [self trimSpaces];
  913.   tempStr = [[[DAYString alloc] init] allocateBuffer:length];
  914.   while (count<length) {
  915.     while (buffer[count]!=' ') {
  916.       [tempStr addChar:buffer[count]];
  917.       count++;
  918.      }
  919.     if ((count<length) && (buffer[count] == ' ')) {
  920.       [tempStr addChar:buffer[count]];
  921.       count++;
  922.      }
  923.     if ((count<length) && (buffer[count]==' ') && 
  924.        ((buffer[count-2] == ':') || (buffer[count-2] =='.'))) {
  925.          [tempStr addChar:buffer[count]];
  926.          count++;
  927.      }
  928.     while (buffer[count]==' ') count++;
  929.    }
  930.   [self takeStringValue:tempStr];
  931.   [tempStr free];
  932.   return self;
  933. }
  934.  
  935. - (const char *)rindex:(char)aChar
  936. {
  937.     return [self rindex:aChar occurenceNum:1 caseSensitive:YES];
  938. }
  939.  
  940. - (const char *)rindex:(char)aChar occurenceNum:(int)n
  941. {
  942.     return [self rindex:aChar occurenceNum:n caseSensitive:YES];
  943. }
  944.  
  945. - (const char *)rindex:(char)aChar caseSensitive:(BOOL)sense
  946. {
  947.     return [self rindex:aChar occurenceNum:1 caseSensitive:sense];
  948. }
  949.  
  950. - (const char *)rindex:(char)aChar occurenceNum:(int)n caseSensitive:(BOOL)sense
  951. {
  952.   int num;
  953.   num = [self rspotOf:aChar occurenceNum:n caseSensitive:sense];
  954.   if (num == -1) return NULL;
  955.   return buffer+num;
  956. }
  957.  
  958. - (const char *)index:(char)aChar
  959. {
  960.     return [self index:aChar occurenceNum:1 caseSensitive:YES];
  961. }
  962.  
  963. - (const char *)index:(char)aChar occurenceNum:(int)n
  964. {
  965.     return [self index:aChar occurenceNum:n caseSensitive:YES];
  966. }
  967.  
  968. - (const char *)index:(char)aChar caseSensitive:(BOOL)sense
  969. {
  970.     return [self index:aChar occurenceNum:1 caseSensitive:sense];
  971. }
  972.  
  973. - (const char *)index:(char)aChar occurenceNum:(int)n caseSensitive:(BOOL)sense
  974. {
  975.   int num;
  976.   num = [self spotOf:aChar occurenceNum:n caseSensitive:sense];
  977.   if (num==-1) return NULL;
  978.   return buffer+num;
  979. }
  980.  
  981. - (int)endcmp:(const char *)aString
  982. {
  983.   return [self endcmp:aString n:-1];
  984. }
  985.  
  986. - (int)endcmp:(const char *)aString n:(int)n
  987. {
  988.   id tempStr;
  989.   int retval;
  990.   if (!aString) return -1;
  991.   tempStr = [DAYString newWithString:aString];
  992.   retval = [self endCompareTo:tempStr n:n caseSensitive:YES];
  993.   [tempStr free];
  994.   return retval;
  995. }
  996.  
  997. - (int)endcasecmp:(const char *)aString
  998. {
  999.   return [self endcasecmp:aString n:-1];
  1000. }
  1001.  
  1002. - (int)endcasecmp:(const char *)aString n:(int)n
  1003. {
  1004.   id tempStr;
  1005.   int retval;
  1006.   if (!aString) return -1;
  1007.   tempStr = [DAYString newWithString:aString];
  1008.   retval = [self endCompareTo:tempStr n:n caseSensitive:NO];
  1009.   [tempStr free];
  1010.   return retval;
  1011. }
  1012.  
  1013. - (int)endCompareTo:(id)sender
  1014. {
  1015.   return [self endCompareTo:sender n:-1 caseSensitive:YES];
  1016. }
  1017.  
  1018. - (int)endCompareTo:(id)sender caseSensitive:(BOOL)sense
  1019. {
  1020.   return [self endCompareTo:sender n:-1 caseSensitive:sense];
  1021. }
  1022.  
  1023. - (int)endCompareTo:(id)sender n:(int)n
  1024. {
  1025.   return [self endCompareTo:sender n:n caseSensitive:YES];
  1026. }
  1027.  
  1028. - (int)endCompareTo:(id)sender n:(int)n caseSensitive:(BOOL)sense
  1029. { // DAY: fixed it: sender doesn't have to be a DAYString anymore.
  1030.     id tempStr, temp2, temp3,
  1031.         smaller = ((length >= [sender length]) ? sender : self);
  1032.     int retval, smallLen;
  1033.     if (!([sender respondsTo:@selector(stringValue)])) return -1;
  1034.     if (![sender stringValue] || !buffer) return -1;
  1035.     if ([sender respondsTo:@selector(length)]) smallLen = [smaller length];
  1036.     else smallLen = strlen([smaller stringValue]);
  1037.     if ((n == -1) || (n > smallLen)) n = smallLen;
  1038.     tempStr = [self right:n];
  1039.     if ([sender respondsTo:@selector(right:)]) temp2 = [sender right:n];
  1040.     else {
  1041.         temp3 = [DAYString newWithString:[sender stringValue]];
  1042.         temp2 = [temp3 right:n];
  1043.         [temp3 free];
  1044.     }
  1045.     retval = [tempStr compareTo:temp2 caseSensitive:sense];
  1046.     [tempStr free];
  1047.     [temp2 free];
  1048.     return retval;
  1049. }
  1050.  
  1051. - replaceCharAt:(int)index with:(char)aChar
  1052. {    // no change if index is out of range.
  1053.     // Other option is to limit index to be in range.  which is better?
  1054.   if ((index < 0) || (index >= length)) return self;
  1055.  
  1056.   if (aChar == 0) return self; // should this return nil? DAY: leave it.
  1057.  
  1058.   buffer[index] = aChar;
  1059.   return self;
  1060. }
  1061.  
  1062. - replaceFrom:(int)start length:(int)len withChar:(char)aChar
  1063. {
  1064.     id retval,tempStr;
  1065.    
  1066.     if (aChar == 0) return nil;
  1067.     tempStr = [[[DAYString alloc] init] addChar:aChar];
  1068.     retval = [self  replaceFrom:start length:len
  1069.                     withString:tempStr];
  1070.     [tempStr free];
  1071.     return retval;
  1072. }
  1073.  
  1074. - replaceFrom:(int)start to:(int)end withChar:(char)aChar
  1075. {
  1076.   return [self replaceFrom:start length:end-start+1 withChar:aChar];
  1077. }
  1078.  
  1079. @end
  1080.